(function($) {
	var specials = {
		'`': 192, '-': 109, '=': 107, '[': 219, ']': 221, '\\': 220,
		'WINKEY': 91, 'ESC': 27, 'ESCAPE': 27, 'TAB': 9, 'SPACE': 32, 'RETURN': 13, 'ENTER': 13,'BACKSPACE': 8,
		'SCROLLLOCK': 145, 'CAPSLOCK': 20, 'NUMLOCK': 144,
		'PAUSE': 19, 'BREAK': 19,
		'INSERT': 45, 'HOME': 36, 'DELETE': 46, 'END': 35, 'PAGEUP': 33, 'PAGEDOWN': 34,
		'LEFT': 37, 'UP': 38, 'RIGHT': 39, 'DOWN': 40,
		'F1': 112, 'F2': 113, 'F3': 114, 'F4': 115, 'F5': 116, 'F6': 117, 'F7': 118, 'F8': 119, 'F9': 120, 'F10': 121, 'F11': 122, 'F12': 123
	};

	$.hotkey = function(params, fn) {
		return $(window).hotkey(params, fn);
	};

	$.unhotkey = function(params, fn) {
		return $(window).unhotkey(params, fn);
	};

	$.pause_hotkey = function(params, fn) {
		return $(window).pause_hotkey(params, fn);
	};

	$.unpause_hotkey = function(params, fn) {
		return $(window).unpause_hotkey(params, fn);
	};

	var get_hotkey_index = function(params) {
		if ( !params ) return false;
		if ( typeof params == 'string' ) {
			var parts = params.split(' ');
			params = {};
			for ( var i = 0, part; i<parts.length && (part = parts[i].toUpperCase()); i++ ) {
				switch(part) {
					case 'CTRL':
					case 'META':
						params.ctrl = true;
						break;
					case 'SHIFT':
						params.shift = true;
						break;
					case 'ALT':
						params.alt = true;
						break;
					default:
						params.key = part;
				}
			}
		}

		var index = specials[params.key];
		if ( !index ) index = params.key.charCodeAt(0);
		if ( params.ctrl ) index += '+ctrl';
		if ( params.shift ) index += '+shift';
		if ( params.alt ) index += '+alt';
		if ( params.meta ) index += '+meta';

		return index;
	};

	var binds = {};
	var callbacks = {};

	$.fn.pause_hotkey = function(params, fn) {
		var index = get_hotkey_index(params);

		return this.each(function() {
			if ( !binds[this] || !callbacks[this] ) return;
			if ( !params ) {
				for ( var index in binds[this] ) {
					if ( binds[this][index] ) binds[this][index].paused = true;
				}
			} else {
				if ( binds[this] && binds[this][index] ) binds[this][index].paused = true;
			}
		});
	};

	$.fn.unpause_hotkey = function(params, fn) {
		var index = get_hotkey_index(params);

		return this.each(function() {
			if ( !binds[this] || !callbacks[this] ) return;
			if ( !params ) {
				for ( var index in binds[this] ) {
					if ( binds[this][index] ) binds[this][index].paused = false;
				}
			} else {
				if ( binds[this] && binds[this][index] ) binds[this][index].paused = false;
			}
		});
	};

	$.fn.unhotkey = function(params, fn) {
		var index = get_hotkey_index(params);

		return this.each(function() {
			var $this = $(this);

			if ( !binds[this] || !callbacks[this] ) return;

			if ( !fn ) {
				var has_index = false;
				for ( var index_key in binds[this] ) {
					if ( index_key == index ) {
						binds[this][index_key] = null;
					} else if ( binds[this][index_key] ) {
						has_index = true;
					}
				}

				if ( !has_index ) {
					$this
						.unbind('keydown', callbacks[this]);

					callbacks[this] = null;
				}
			} else {
				var has_index = false;
				for ( var index_key in binds[this] ) {
					if ( index_key == index ) {
						var has_fn = false;
						for ( var ci = 0, cfn; ci < binds[this][index_key].callbacks.length && (cfn = binds[this][index_key].callbacks[ci]); ci++ ) {
							if ( fn == cfn ) {
								binds[this][index_key].callbacks[ci] = null
							} else if ( cfn != null ) {
								has_fn = true;
							}
						}
						if ( !has_fn ) binds[this][index_key] = null;
					} else if ( binds[this][index_key] ) {
						has_index = true;
					}
				}

				if ( has_index ) {
					has_index = false;
					for ( var index_key in binds[this] ) {
						if ( binds[this][index_key] ) {
							has_index = true;
							break;
						}
					}
				}

				if ( !has_index ) {
					$this
						.unbind('keydown', callbacks[this]);

					callbacks[this] = null;
				}
			}
		});
	};

	$.fn.hotkey = function(params, fn) {
		var index = get_hotkey_index(params);

		return this.each(function() {
			var $this = $(this);
			var node = this;
			var has_bind = false;

			if ( !binds[node] ) {
				binds[node] = {};
			} else {
				has_bind = true;
			}

			if ( !binds[node][index] ) {
				binds[node][index] = {};
				binds[node][index].callbacks = [];
			}

			binds[node][index].callbacks[binds[node][index].callbacks.length] = fn;

			if ( !callbacks[node] ) {
				callbacks[node] = function(e) {
					if ( e.keyCode == 16 || e.keyCode == 17 || e.keyCode == 18 ) return true;
					var kindex = e.keyCode;
					if ( e.ctrlKey || e.metaKey ) kindex += '+ctrl';
					if ( e.shiftKey ) kindex += '+shift';
					if ( e.altKey ) kindex += '+alt';

					if ( binds[node][kindex] && !binds[node][kindex].paused ) {
						var hs = binds[node][kindex].callbacks;
						for ( var i = 0, h; i < hs.length && (h = hs[i]); i++ ) {
							h();
						}
						return false;
					}
				};
				$this
					.bind('keydown', callbacks[node]);
			}

		});
	};

})(jQuery);